Awesome Smarthome hub

Really Awesome
A Project By Ximeng Zhang.


Demonstration Video


Introduction

Being an investor for years, it has been a normal for me to unlock my cellphone, unlock my broker account many times in a day to check my portofolio which becomes annoying sometimes. Also, I have already get used to write down a to-do list for the day and finish everything on the list. But that means I have to open a OneNote or some software that is not always in my sight to remind me. Last but not least, why not have a camera to monitor whats going on in my room that nobody is trying to steal my knowledge in my notebooks?

So this project solves all above problem in one place, the hub will always tell you your current portofolio, what you should do next, whats going on in the room and always there on your desk. You don't have to unlock anything to see it. It even has bonus features that tell you the weather, temperature, and humidty outside, even for the whole week, and your room temperature so you know your ac is working. It is implemented as a pygame script and consists of multiple library and api calls. See design section for details.


Generic placeholder image

Project Objective:

  • Pi should be able to host a website to stream camera content
  • GUI on the Pi TFT that can be used to turn on / off the camera
  • GUI on the Pi TFT that can show weather time and date
  • GUI on the Pi TFT that can show future weather in a week
  • GUI on the Pi TFT that can show and edit stock portofolio
  • GUI on the Pi TFT that can show and edit a to-do list
  • GUI on the Pi TFT that can shutdown/reboot the pi and start automatically when reboot

Design

Mainpage Section

The main page contains several section where the api and more detail information can be found in each section.

The main page is implemented in a while loop that will draw a screen with all above elements and refresh at approximately 10Hz. More detail about the refresh of each section see refresh control in test section. After drawing all elements onto the screen canvas, it will check for touch event, if the touch area is in the specific area for each section, it would call the function code to enter the 2nd level menu. Each function would print different things on the same screen and update canvas while the main loop is not running. After the code finish, the program would return to the main loop and refresh as in the main page. The main loop would be terminated if the quit button is detected to be pressed. The code is following:

while flag:  
	screen.fill(BLACK)
	screen.blit(background_surface, (0, 0))
	
	ui_manager.draw_ui(screen)
	#======================================================
	#
	#======================================================
	# display date and time
	
	
	date = display_date()
	# display a moon if its late in the night 
	if int(str(date[3][:2])) >= 21:
		ball1 = pygame.image.load("moon.png")
		ballrect1 = ball1.get_rect()
		ballrect1 = ballrect1.move(inits1)
		screen.blit(ball1, ballrect1)
	text = my_font1.render(str(date[3]), True, RED)
	textRect = text.get_rect(center=(250,30))  
	screen.blit(text, textRect)
	text = my_font1.render(str(date[0]), True, green)
	textRect = text.get_rect(center=(190,30))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[1]), True, WHITE)
	textRect = text.get_rect(center=(200,50))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[2]), True, WHITE)
	textRect = text.get_rect(center=(225,50))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[4]), True, WHITE)
	textRect = text.get_rect(center=(260,50))  
	screen.blit(text, textRect)
	


	#======================================================
	# display functions   
	for my_text, text_pos in my_buttons.items():    
		text_surface = my_font.render(my_text, True, WHITE)    
		rect = text_surface.get_rect(center=text_pos)
		screen.blit(text_surface, rect) 

	#=======================================================
	#display weather
	#update frequency: every 10 minutes
	if counter == 0 or counter % 6000 == 0:
		weather = current_weather()
	if 'Rain' in weather[0] : 
		ball = pygame.image.load("Rain.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
		
	elif 'Sunny' in weather[0] or 'lear' in weather[0]:
		ball = pygame.image.load("sun.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'Wind' in weather[0]:
		ball = pygame.image.load("Wind.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'now' in weather[0]:
		ball = pygame.image.load("Snow.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'louds' in weather[0]:
		ball = pygame.image.load("Cloud.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'unde' in weather[0]:
		ball = pygame.image.load("Thunder.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	else:
		text = my_font1.render(str(weather[0]), True, green)
		textRect = text.get_rect(center=weather_position[0])  
		screen.blit(text, textRect)
	screen.blit(ball, ballrect)
	
	for i in range(1,3):
		text = my_font.render(str(weather[i]), True, WHITE)
		textRect = text.get_rect(center=weather_position[i])  
		screen.blit(text, textRect)
	#==================================================
	#display room temperature
	#update frequency: every 10 minutes	
	if counter == 0 or counter % 6000 == 0:
		temp_data = room_temp()
	temp = 'room temp: '+ str(temp_data) + '°C'
	text = my_font.render(temp, True, WHITE)
	textRect = text.get_rect(center=(60,60))  
	screen.blit(text, textRect)
	#=======================================================
	#print To-do list 

	htm_text_block_2 = UITextBox(todo_text+todo_more,
							 pygame.Rect((150, 70), (140, 130)),
							 manager=ui_manager,
							 object_id="#text_box_2")
	#=======================================================
	# print your portofolio
	#update every 10 minutes
	if counter == 0 or counter % 6000 == 0 or refresh:
		portofolio = 0
		if stocks:
			for items in stocks:
				price = stock_price(items)
				portofolio += float(price) * stocks[items]
		refresh = False
	text = my_font1.render('Portofolio: ', True, green)
	textRect = text.get_rect(center=(60,130))  
	screen.blit(text, textRect)
	text = my_font.render(str(portofolio)[:9], True, WHITE)
	textRect = text.get_rect(center=(60,160))  
	screen.blit(text, textRect)
	#======================================================
	# Detect touch on screen to determine next move
	for event in pygame.event.get():                
		if(event.type is MOUSEBUTTONUP):
			# Erase the Work space            
			pos = pygame.mouse.get_pos() 
			x,y = pos
			# go to the next level if pressed button.
			if y > 200:                
				if x < 160:    
					print ('quit button pressed')
					flag = False
					break
				else:
					function_menu()
			elif y < 40 and x < 110 :
				weather_menu()
			elif 70 < y < 150 and  100 < x < 250:
				Todo_menu()
			elif 120< y < 190 and 0 <x <130:
				stock_menu()
				refresh = True
	if(not time.time() - start < 300):
		flag = False
	pygame.display.flip() 
	#update counter every 0.1 second. Refresh counter every hour.
	time.sleep(0.1)
	counter += 1
	if counter > 36000:
		counter = 0

Weather Section

Instead of getting a temperature and humidity sensor. I found that its easier to just use a API to get both outdoor humidity and weather at once. And just use one temperature sensor to show the room temperature. Then I registered at open weather map and got a key. By using API from open weather map, I can get the weather, temperature and humidity at once. The weather api isopenweathermap.org. The following api call would give me the weather data for the current day.

def current_weather():
              api = 'http://api.openweathermap.org/data/2.5/weather?zip=14850,us&appid=818eb27f395ca0f860e5ce34fedade0f'
              data = requests.get(api).json()
              weather = data['weather'][0]['main']
              temp_raw = data['main']['temp']-273.15
              temp = str(temp_raw)[:4]+ '°C'
              hum = data['main']['humidity']
              weather_info = [weather,temp,hum]
              return weather_info
            

By using this api call, I can obtain the weather data of next 7 days.
def future_weather():
            api = 'https://api.openweathermap.org/data/2.5/onecall?lat=42.44&lon=-76.5&appid=818eb27f395ca0f860e5ce34fedade0f'
            data = requests.get(api).json()
            weather_info = []
            for i in range(0,7):
              weather = data['daily'][i]['weather'][0]['main']
              weather_info.append(weather)
              temp_raw = data['daily'][i]['temp']['day'] - 273.15
              temp = str(temp_raw)[:4]+ '°C'
              weather_info.append(temp)
            print(weather_info)
            return weather_info 
          
Then the 2nd level menu for the weather should gave the weather information for the 7 days. The timeout was set to 5 seconds because there is no additional operation needed on this page.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def weather_menu():
	weather = future_weather()
	screen = pygame.display.set_mode((320, 240))
	background = pygame.Surface((320, 240))
	background.fill(pygame.Color('#000000'))

	weeklist = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
	weeklistnum = {'Mon':0, 'Tue':1,'Wed':2,'Thu':3,'Fri':4,'Sat':5,'Sun':6}
	date = display_date()
	today = weeklistnum[str(date[0])]
	# print this week's weather
	daycounter = 0
	weathercounter = 0
	weekdaycenters = [(30,15),(30,50),(30,85),(30,120),(30,155),(30,190),(30,225)]
	weathercenters = [(90,0),(90,35),(90,70),(90,105),(90,140),(90,175),(90,210)]
	tempcenters = [(230,15),(230,50),(230,85),(230,120),(230,155),(230,190),(230,225)]
	for item in weeklist[today:]:
		text = my_font1.render(item, True, green)
		textRect = text.get_rect(center=weekdaycenters[daycounter])  
		screen.blit(text, textRect)
		if 'Rain' in weather[weathercounter] : 
			ball = pygame.image.load("Rain.png")	
		elif 'Sunny' in weather[weathercounter]:
			ball = pygame.image.load("sun.png")
		elif 'Wind' in weather[weathercounter]:
			ball = pygame.image.load("Wind.png")
		elif 'now' in weather[weathercounter]:
			ball = pygame.image.load("Snow.png")
		elif 'louds' in weather[weathercounter]:
			ball = pygame.image.load("Cloud.png")
		elif 'unde' in weather[weathercounter]:
			ball = pygame.image.load("Thunder.png")
		else:
			ball = my_font1.render(str(weather[weathercounter]), True, green)
		ballrect = ball.get_rect()
		ballrect = ballrect.move(weathercenters[daycounter])	
		screen.blit(ball, ballrect)
		weathercounter +=1
		text = my_font1.render(str(weather[weathercounter]), True, green)
		textRect = text.get_rect(center=tempcenters[daycounter])  
		screen.blit(text, textRect)
		weathercounter +=1
		daycounter +=1
	for item in weeklist[:today]:
		text = my_font1.render(item, True, green)
		textRect = text.get_rect(center=weekdaycenters[daycounter])  
		screen.blit(text, textRect)
		if 'Rain' in weather[weathercounter] : 
			ball = pygame.image.load("Rain.png")	
		elif 'Sunny' in weather[weathercounter]:
			ball = pygame.image.load("sun.png")
		elif 'Wind' in weather[weathercounter]:
			ball = pygame.image.load("Wind.png")
		elif 'now' in weather[weathercounter]:
			ball = pygame.image.load("Snow.png")
		elif 'louds' in weather[weathercounter]:
			ball = pygame.image.load("Cloud.png")
		elif 'unde' in weather[weathercounter]:
			ball = pygame.image.load("Thunder.png")
		else:
			ball = my_font1.render(str(weather[weathercounter]), True, green)
		ballrect = ball.get_rect()
		ballrect = ballrect.move(weathercenters[daycounter])	
		screen.blit(ball, ballrect)
		weathercounter +=1
		text = my_font1.render(str(weather[weathercounter]), True, green)
		textRect = text.get_rect(center=tempcenters[daycounter])  
		screen.blit(text, textRect)
		weathercounter +=1
		daycounter +=1
		
	
	pygame.display.flip()
	time.sleep(5)
The icons were used to beautify the UI and found at iconfinder. The future weather menu :

Generic placeholder image

Time and date

The time and date is a simple call to the function time.ctime() and split the string into sctions.

def display_date():
	currenttime = str(time.ctime()).split(' ')
	return currenttime
Then display different sections of time onto the screen. When its later than 9pm in the evening, a moon icon would be displayed on the screen. The time and date section is refreshed at approximatly 10Hz on the main page. See main loop code for implementation.

To-do list

The To-do list is implemented using a library called Pygame_GUI. It provides a nice-looking text box which would take html style string as input and display on the screen. Two string is used in the code.

todo_text = '<font face=fira_code size=2 color=#000000><b>To-Do</b>'+'<br><br>'
todo_more = '5725 Lab and project' +'<br><br>'+'research'
The first string was used to display To-Do and the next string can be modified by keyboard input and change the to-do event.
def Todo_menu():
	global todo_more
	textinput = pygame_textinput.TextInput()
	clock = pygame.time.Clock()
	todoflag = True
	while todoflag:
		screen.fill((225, 225, 225))

		events = pygame.event.get()
		for event in events:
			if event.type == pygame.QUIT:
				exit()
				

		# Feed it with events every frame
		if textinput.update(events):
			todo_more = textinput.get_text()
			print(todo_more)
			todoflag = False
		# Blit its surface onto the screen
		screen.blit(textinput.get_surface(), (10, 10))

		pygame.display.update()
		clock.tick(30)

Function menu

User will enter function menu if they hit system area in the mainpage. The function menu was implemented by adding buttons in pygame_gui library. Each button was assigned specific functions such as returning to the mainpage, system reboot, shutdown, camera on and camera off.

def function_menu():
	global proc
	window_surface = pygame.display.set_mode((320, 240))
	background = pygame.Surface((320, 240))
	background.fill(pygame.Color('#000000'))
	manager = pygame_gui.UIManager((320, 240))
	button1 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 10), (60, 30)),
                                                text='Main',
                                                manager=manager)
	button2 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((95, 10), (60, 30)),
                                                text='Reboot',
                                                manager=manager)
	button3 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 50), (80, 40)),
                                                text='Shutdown',
                                                manager=manager)
	button4 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 100), (100, 40)),
                                                text='Camera On',
                                                manager=manager)
	button5 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 150), (100, 40)),
                                                text='Camera Off',
                                                manager=manager)
                        
	clock = pygame.time.Clock()
	is_running = True

	while is_running:

		time_delta = clock.tick(60)/1000.0
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				is_running = False

			if event.type == pygame.USEREVENT:
				if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
					if event.ui_element == button1:
						is_running = False
					elif event.ui_element == button2:
						os.system('sudo reboot')
					elif event.ui_element == button3:
						os.system('sudo shutdown')
					elif event.ui_element == button4:
						proc = subprocess.Popen(['python3','videostream.py'])
					elif event.ui_element == button5:
						proc.terminate()
			manager.process_events(event)
		
		manager.update(time_delta)
		window_surface.blit(background, (0, 0))
		manager.draw_ui(window_surface)

		pygame.display.update()

Generic placeholder image

Stock Section

The stock page is the most complicated part of the project. It use yahoo_fin library and call live_stock_price() to get live stock price. The stock holding information is stored in a dictionary. Each stock and corresponding shares. stocks = {'msft':3,'aapl':2,'amzn':1,'nflx':3} The mainpage loop would calculate the total value based on the stock info inside the dictionary by multiply each stock price by shares and sum.

The stock menu consists of more options. It allows the user to see which stocks and corresponding shares he/she is currently holding. Each page will display 3 stocks and prices.The user can go to the next page of stocks by click next and previous page by click pre. By clicking the plus and minus button on the right hand side of the screen, the user can add/reduce the shares of stocks he/she is currently holding. This is implemented by having a all_stocks set and another set of current_stocks. The current_stocks will record which 3 stocks are currently being displayed on the screen. A pointer will record the position of current_stocks in the all_stocks and clicking on the pre or next would modify the pointer and thus change the content on the screen. Since the API call is slow, the total value of stocks would only be updated if someone marks changes in the dictionary. And another dictionary called stockprices is included to store the prices of stocks so the program do not need to make API call each time it needs to print the price info of the stocks. Just like main loop, event detect would run after printing everything on the screen. If add/reduce is clicked, the corresponding stock would be search in the dictionary and mark as changes made and force the loop to calculate new total value. Similar code to the to-do list was used to add stocks into the dictionary which can be found in Appendix.

def stock_menu():
	rich = True
	pluscenter = [(250,80),(250,130),(250,180)]
	minuscenter = [(290,80),(290,130),(290,180)]
	stockcenter = [(50,80),(50,130),(50,180)]
	sharecenter = [(170,80),(170,130),(170,180)]
	pricecenter = [(120,80),(120,130),(120,180)]
	stockpointer = 0

	changes = True
	
	while rich:
		screen.fill(BLACK)
		
		
		if changes:
			all_stocks = []
			for stock in stocks:
				all_stocks.append(stock)
			portofolio = 0
			for items in stocks:
				stockprices[items] = stock_price(items)
				portofolio += float(stockprices[items]) * stocks[items]
			changes = False
		text = my_font1.render('Portofolio: ', True, green)
		textRect = text.get_rect(center=(60,10))  
		screen.blit(text, textRect)
		text = my_font.render(str(portofolio)[:9], True, WHITE)
		textRect = text.get_rect(center=(190,10))  
		screen.blit(text, textRect)
		text = my_font.render('Watchlist ', True, RED)
		textRect = text.get_rect(center=(50,40))  
		screen.blit(text, textRect)
		text = my_font.render('Price ', True, WHITE)
		textRect = text.get_rect(center=(120,40))  
		screen.blit(text, textRect)
		text = my_font.render('Shares ', True, WHITE)
		textRect = text.get_rect(center=(170,40))  
		screen.blit(text, textRect)
		
		for i in range(0,3):
			text = my_font1.render(' + ', True, WHITE)
			textRect = text.get_rect(center=pluscenter[i])  
			screen.blit(text, textRect)
		for i in range(0,3):
			text = my_font1.render(' - ', True, WHITE)
			textRect = text.get_rect(center=minuscenter[i])  
			screen.blit(text, textRect)
		i = 0
		current_stocks = all_stocks[stockpointer:stockpointer+3]
		for stock in current_stocks:
			text = my_font.render(stock, True, WHITE)
			textRect = text.get_rect(center=stockcenter[i])  
			screen.blit(text, textRect)
			text = my_font.render(str(stocks[stock]), True, WHITE)
			textRect = text.get_rect(center=sharecenter[i])  
			screen.blit(text, textRect)
			text = my_font.render(stockprices[stock], True, WHITE)
			textRect = text.get_rect(center=pricecenter[i])  
			screen.blit(text, textRect)
			i += 1
		for my_text, text_pos in stock_buttons.items():    
			text_surface = my_font.render(my_text, True, WHITE)    
			rect = text_surface.get_rect(center=text_pos)
			screen.blit(text_surface, rect) 
		
		for event in pygame.event.get():                
			if(event.type is MOUSEBUTTONUP):
				# Erase the Work space            
				pos = pygame.mouse.get_pos() 
				x,y = pos
				# go to the next level if pressed button.
				if y > 200:                
					if x >250:    
						rich = False
						break
					elif x >190:
						stockpointer += 3
					elif x >130:
						stockpointer -= 3
					else:
						add_stock()
						changes = True
				elif 170<y<190:
					if 280 <x< 300:
						stocks[current_stocks[2]] -= 1
						changes =True
					elif 240<x<260:
						stocks[current_stocks[2]] += 1
						changes = True
				elif 120<y<140 :
					if 280 <x< 300:
						stocks[current_stocks[1]] -= 1
						changes = True
					elif 240<x<260:
						stocks[current_stocks[1]] += 1
						changes = True
				elif 70<y<90 :
					if 280 <x< 300:
						stocks[current_stocks[0]] -= 1
						changes = True
					elif 240<x<260:
						stocks[current_stocks[0]] += 1
						changes = True
		pygame.display.flip()	

Generic placeholder image

Start when reboot

The project would automatically open when the system reboot. It was achieved by modifing the .bashrc file by adding ./fix_touchscreen and cd final/ and sudo python3 app.py and setting the pi to auto login by setting using sudo raspi-config

Camera streaming

Raspberry Pi camera was installed following this tutorial. First insert the connector of the camera in the slot in the Raspberry Pi, Then ran sudo raspi-config in the terminal to enable the camera. This is achieved by selecting 5 Interfacing Options -> P1 Camera -> Yes. After enabling the camera, reboot of (sudo reboot)the Raspberry Pi is necessary. For livestreaming video, I found the tutorial in past project "go 2 class 4 me" and followed the following tutorial and used this code to stream the live video feed to a website http://<Your_Pi_IP_Address>:8000. The user can access the video streaming through the same WiFi network and view the content with very small latency.

Generic placeholder image

Camera control

The camera control was done by using the subprocess popen and terminate function.

elif event.ui_element == button4:
						proc = subprocess.Popen(['python3','videostream.py'])
            elif event.ui_element == button5:
              proc.terminate()
  


Testing

Refresh rate control

One error experienced in the project was that too many api calls were made and further use was prohibited and caused error. Another error is that the live stock price api call is particularly slow that it would take about 1 second to check a stock's price. Thus frequent call to the api would cause error and lag to the UI. Running in multithread would solve part of the problem but another approach is to refresh the data of the each section at different rate. The Weather, stock and room temperature are set to be refreshed every 10 minutes while the time and date is updated each 0.1 second by using combination of counter check and time.sleep(). See mainpage in design to see the code.

PiTFT freeze problem

The project was stuck for days for having a issue that code would run on the monitor just fine but would freeze the piTFT at console after run "python3 app.py" without even showing the pygame window. After consulting professor Skovira, we found that the error was caused by a library called Pygame_functions which was used to take text input for to-do list and stock adding operation. The exact reason why it would cause the error is still unknown but it would direct the code to display the content on monitor, so every time the code runs, the program always seek to be run on the monitor and cause the freeze of piTFT. The problem was solved by using another Pygame text input Library.

PiTFT touchscreen error

By running 'ls -l /dev/input/touchscreen', I can check whether the touchscreen is fucntion normally. However, the touchscreen function often break for reason unkown. If the project is automatically runned without running ./fix_touchscreen the touchscreen would not respond to anything. So in the .bashrc file, ./fix_touchscreen is runned before the 'sudo python3 app.py'


Result

Overall, the project is successful. It demonstrated all goals: stock section, to-do list section, weather section, open and close camera streaming from the GUI, display indoor temperature and show current time and date.

Another input should be add to the project is the location of the user so that user can modify the location of the weather section. Multiple sensors and controllers like the temperature sensor in the project can be added to the pi so it acts more like a 'smart home hub'. Motor can be attached to the camera so user can have more control of the pi. If it is placed in a mirror and connect to a bluetooth module, it would be a great smart mirror project(touchscreen need to be fixed). For the stock section of the project, options can also be added to the section but need to be refreshed more frequently. To-do list can be implemented as a queue that user should be able to maintain the finished items and display the next missions and append new missions to be finished. Overall the project is very expandable and can add so many more features to it.


Generic placeholder image

Simon (Ximeng) Zhang

xz737@cornell.edu

ximengzhang.com

Designed all software (Just being myself).


Parts List

Total: $69.95


References

PiCamera Document
Camera streaming tutorial
"go 2 class 4 me"
Bootstrap
Pigpio Library
Pygame Library
Pygame_GUI Library
Pygame text input Library
Pygame_functions Library
Yahoo_fin Library
Code embedding
R-Pi GPIO Document

Code Appendix

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# -*- coding: utf-8 -*-
import pygame     # Import pygame graphics library
import time
import requests
import smbus
import requests
import pygame_gui
import os
from pygame.locals import *   # for event MOUSE variables
from yahoo_fin.stock_info import *
import subprocess
from pygame_gui.ui_manager import UIManager
from pygame_gui.elements.ui_text_box import UITextBox
from pygame_gui.core import IncrementalThreadedResourceLoader
#from pygame_functions import *
import pygame_textinput


# os.putenv('SDL_VIDEODRIVER', 'fbcon')   # Display on piTFT
# os.putenv('SDL_FBDEV', '/dev/fb1')     
# os.putenv('SDL_MOUSEDRV', 'TSLIB')     # Track mouse clicks on piTFT
# os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
pygame.init()
pygame.mouse.set_visible(False)

proc = 0
todo_text = '<font face=fira_code size=2 color=#000000><b>To-Do</b>'+'<br><br>'
todo_more = '5725 Lab and project' +'<br><br>'+'research'
WHITE = 255, 255, 255
BLACK = 0,0,0
RED=255,0,0

green = (0, 255, 0) 
blue = (0, 0, 128) 
screen_size = (320,240)
screen = pygame.display.set_mode(screen_size)
my_font= pygame.font.Font("COMIC.TTF", 13)
my_font1 = pygame.font.Font(None,30)
my_buttons= { 'quit':(80,210), 'system':(240,210)}

start = time.time()
weather_position = [(30,30),(85,25),(120,25)]
background_surface = pygame.Surface(screen_size)
background_surface.fill(pygame.Color("#000000"))

loader = IncrementalThreadedResourceLoader()
clock = pygame.time.Clock()
ui_manager = UIManager(screen_size, 'data/themes/theme_1.json', resource_loader=loader)
ui_manager.add_font_paths("Montserrat",
						  "data/fonts/Montserrat-Regular.ttf",
						  "data/fonts/Montserrat-Bold.ttf",
						  "data/fonts/Montserrat-Italic.ttf",
						  "data/fonts/Montserrat-BoldItalic.ttf")

ui_manager.preload_fonts([{'name': 'Montserrat', 'html_size': 4.5, 'style': 'bold'},
						  {'name': 'Montserrat', 'html_size': 4.5, 'style': 'regular'},
						  {'name': 'Montserrat', 'html_size': 2, 'style': 'regular'},
						  {'name': 'Montserrat', 'html_size': 2, 'style': 'italic'},
						  {'name': 'Montserrat', 'html_size': 6, 'style': 'bold'},
						  {'name': 'Montserrat', 'html_size': 6, 'style': 'regular'},
						  {'name': 'Montserrat', 'html_size': 6, 'style': 'bold_italic'},
						  {'name': 'Montserrat', 'html_size': 4, 'style': 'bold'},
						  {'name': 'Montserrat', 'html_size': 4, 'style': 'regular'},
						  {'name': 'Montserrat', 'html_size': 4, 'style': 'italic'},
						  {'name': 'fira_code', 'html_size': 2, 'style': 'regular'},
						  {'name': 'fira_code', 'html_size': 2, 'style': 'bold'},
						  {'name': 'fira_code', 'html_size': 2, 'style': 'bold_italic'}
						  ])
loader.start()
finished_loading = False
while not finished_loading:
	finished_loading, progress = loader.update()

# initialize parameters 
flag = True
portofolio = 0
stocks = {'msft':3,'aapl':2,'amzn':1,'nflx':3}
stockprices = {'msft':'','aapl':'','amzn':'','nflx':''}
counter = 0
inits = [0,10]
inits1 = [200,10]

def current_weather():
	api = 'http://api.openweathermap.org/data/2.5/weather?zip=14850,us&appid=818eb27f395ca0f860e5ce34fedade0f'
	data = requests.get(api).json()
	weather = data['weather'][0]['main']
	temp_raw = data['main']['temp']-273.15
	temp = str(temp_raw)[:4]+ '°C'
	hum = data['main']['humidity']
	weather_info = [weather,temp,hum]
	return weather_info
	
def future_weather():
	api = 'https://api.openweathermap.org/data/2.5/onecall?lat=42.44&lon=-76.5&appid=818eb27f395ca0f860e5ce34fedade0f'
	data = requests.get(api).json()
	weather_info = []
	for i in range(0,7):
		weather = data['daily'][i]['weather'][0]['main']
		weather_info.append(weather)
		temp_raw = data['daily'][i]['temp']['day'] - 273.15
		temp = str(temp_raw)[:4]+ '°C'
		weather_info.append(temp)
	print(weather_info)
	return weather_info 
		
	
def display_date():
	currenttime = str(time.ctime()).split(' ')
	return currenttime
def stock_price(stock_name):
	price = get_live_price(stock_name)
	
	return str(price)[:6]
	
stock_buttons = {'add':(80,210), 'prev':(140,210),'next':(200,210), 'main':(260,210)}
def stock_menu():
	rich = True
	pluscenter = [(250,80),(250,130),(250,180)]
	minuscenter = [(290,80),(290,130),(290,180)]
	stockcenter = [(50,80),(50,130),(50,180)]
	sharecenter = [(170,80),(170,130),(170,180)]
	pricecenter = [(120,80),(120,130),(120,180)]
	stockpointer = 0

	changes = True
	
	while rich:
		screen.fill(BLACK)
		
		
		if changes:
			all_stocks = []
			for stock in stocks:
				all_stocks.append(stock)
			portofolio = 0
			for items in stocks:
				stockprices[items] = stock_price(items)
				portofolio += float(stockprices[items]) * stocks[items]
			changes = False
		text = my_font1.render('Portofolio: ', True, green)
		textRect = text.get_rect(center=(60,10))  
		screen.blit(text, textRect)
		text = my_font.render(str(portofolio)[:9], True, WHITE)
		textRect = text.get_rect(center=(190,10))  
		screen.blit(text, textRect)
		text = my_font.render('Watchlist ', True, RED)
		textRect = text.get_rect(center=(50,40))  
		screen.blit(text, textRect)
		text = my_font.render('Price ', True, WHITE)
		textRect = text.get_rect(center=(120,40))  
		screen.blit(text, textRect)
		text = my_font.render('Shares ', True, WHITE)
		textRect = text.get_rect(center=(170,40))  
		screen.blit(text, textRect)
		
		for i in range(0,3):
			text = my_font1.render(' + ', True, WHITE)
			textRect = text.get_rect(center=pluscenter[i])  
			screen.blit(text, textRect)
		for i in range(0,3):
			text = my_font1.render(' - ', True, WHITE)
			textRect = text.get_rect(center=minuscenter[i])  
			screen.blit(text, textRect)
		i = 0
		current_stocks = all_stocks[stockpointer:stockpointer+3]
		for stock in current_stocks:
			text = my_font.render(stock, True, WHITE)
			textRect = text.get_rect(center=stockcenter[i])  
			screen.blit(text, textRect)
			text = my_font.render(str(stocks[stock]), True, WHITE)
			textRect = text.get_rect(center=sharecenter[i])  
			screen.blit(text, textRect)
			text = my_font.render(stockprices[stock], True, WHITE)
			textRect = text.get_rect(center=pricecenter[i])  
			screen.blit(text, textRect)
			i += 1
		for my_text, text_pos in stock_buttons.items():    
			text_surface = my_font.render(my_text, True, WHITE)    
			rect = text_surface.get_rect(center=text_pos)
			screen.blit(text_surface, rect) 
		
		for event in pygame.event.get():                
			if(event.type is MOUSEBUTTONUP):
				# Erase the Work space            
				pos = pygame.mouse.get_pos() 
				x,y = pos
				# go to the next level if pressed button.
				if y > 200:                
					if x >250:    
						rich = False
						break
					elif x >190:
						stockpointer += 3
					elif x >130:
						stockpointer -= 3
					else:
						add_stock()
						changes = True
				elif 170<y<190:
					if 280 <x< 300:
						stocks[current_stocks[2]] -= 1
						changes =True
					elif 240<x<260:
						stocks[current_stocks[2]] += 1
						changes = True
				elif 120<y<140 :
					if 280 <x< 300:
						stocks[current_stocks[1]] -= 1
						changes = True
					elif 240<x<260:
						stocks[current_stocks[1]] += 1
						changes = True
				elif 70<y<90 :
					if 280 <x< 300:
						stocks[current_stocks[0]] -= 1
						changes = True
					elif 240<x<260:
						stocks[current_stocks[0]] += 1
						changes = True
		pygame.display.flip()	

def add_stock():
	global stocks
	global stockprices
	textinput = pygame_textinput.TextInput()
	clock = pygame.time.Clock()
	todoflag = True
	while todoflag:
		screen.fill((225, 225, 225))

		events = pygame.event.get()
		for event in events:
			if event.type == pygame.QUIT:
				exit()
				

		# Feed it with events every frame
		if textinput.update(events):
			toadd = textinput.get_text().split()
			stocks[toadd[0]] = int(toadd[1])
			
			
			todoflag = False
		# Blit its surface onto the screen
		screen.blit(textinput.get_surface(), (10, 10))

		pygame.display.update()
		clock.tick(30)
	
	

def room_temp():
	global room_temperature
	bus = smbus.SMBus(1)
	addr = 0x48
	roomtemp = bus.read_byte(addr)
	room = roomtemp
	return room
	
	
def function_menu():
	global proc
	window_surface = pygame.display.set_mode((320, 240))
	background = pygame.Surface((320, 240))
	background.fill(pygame.Color('#000000'))
	manager = pygame_gui.UIManager((320, 240))
	button1 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 10), (60, 30)),
                                                text='Main',
                                                manager=manager)
	button2 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((95, 10), (60, 30)),
                                                text='Reboot',
                                                manager=manager)
	button3 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 50), (80, 40)),
                                                text='Shutdown',
                                                manager=manager)
	button4 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 100), (100, 40)),
                                                text='Camera On',
                                                manager=manager)
	button5 = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((10, 150), (100, 40)),
                                                text='Camera Off',
                                                manager=manager)
                        
	clock = pygame.time.Clock()
	is_running = True

	while is_running:

		time_delta = clock.tick(60)/1000.0
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				is_running = False

			if event.type == pygame.USEREVENT:
				if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
					if event.ui_element == button1:
						is_running = False
					elif event.ui_element == button2:
						os.system('sudo reboot')
					elif event.ui_element == button3:
						os.system('sudo shutdown')
					elif event.ui_element == button4:
						proc = subprocess.Popen(['python3','videostream.py'])
					elif event.ui_element == button5:
						proc.terminate()
			manager.process_events(event)
		
		manager.update(time_delta)
		window_surface.blit(background, (0, 0))
		manager.draw_ui(window_surface)

		pygame.display.update()

def weather_menu():
	weather = future_weather()
	screen = pygame.display.set_mode((320, 240))
	background = pygame.Surface((320, 240))
	background.fill(pygame.Color('#000000'))

	weeklist = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
	weeklistnum = {'Mon':0, 'Tue':1,'Wed':2,'Thu':3,'Fri':4,'Sat':5,'Sun':6}
	date = display_date()
	today = weeklistnum[str(date[0])]
	# print this week's weather
	daycounter = 0
	weathercounter = 0
	weekdaycenters = [(30,15),(30,50),(30,85),(30,120),(30,155),(30,190),(30,225)]
	weathercenters = [(90,0),(90,35),(90,70),(90,105),(90,140),(90,175),(90,210)]
	tempcenters = [(230,15),(230,50),(230,85),(230,120),(230,155),(230,190),(230,225)]
	for item in weeklist[today:]:
		text = my_font1.render(item, True, green)
		textRect = text.get_rect(center=weekdaycenters[daycounter])  
		screen.blit(text, textRect)
		if 'Rain' in weather[weathercounter] : 
			ball = pygame.image.load("Rain.png")	
		elif 'Sunny' in weather[weathercounter]:
			ball = pygame.image.load("sun.png")
		elif 'Wind' in weather[weathercounter]:
			ball = pygame.image.load("Wind.png")
		elif 'now' in weather[weathercounter]:
			ball = pygame.image.load("Snow.png")
		elif 'louds' in weather[weathercounter]:
			ball = pygame.image.load("Cloud.png")
		elif 'unde' in weather[weathercounter]:
			ball = pygame.image.load("Thunder.png")
		else:
			ball = my_font1.render(str(weather[weathercounter]), True, green)
		ballrect = ball.get_rect()
		ballrect = ballrect.move(weathercenters[daycounter])	
		screen.blit(ball, ballrect)
		weathercounter +=1
		text = my_font1.render(str(weather[weathercounter]), True, green)
		textRect = text.get_rect(center=tempcenters[daycounter])  
		screen.blit(text, textRect)
		weathercounter +=1
		daycounter +=1
	for item in weeklist[:today]:
		text = my_font1.render(item, True, green)
		textRect = text.get_rect(center=weekdaycenters[daycounter])  
		screen.blit(text, textRect)
		if 'Rain' in weather[weathercounter] : 
			ball = pygame.image.load("Rain.png")	
		elif 'Sunny' in weather[weathercounter]:
			ball = pygame.image.load("sun.png")
		elif 'Wind' in weather[weathercounter]:
			ball = pygame.image.load("Wind.png")
		elif 'now' in weather[weathercounter]:
			ball = pygame.image.load("Snow.png")
		elif 'louds' in weather[weathercounter]:
			ball = pygame.image.load("Cloud.png")
		elif 'unde' in weather[weathercounter]:
			ball = pygame.image.load("Thunder.png")
		else:
			ball = my_font1.render(str(weather[weathercounter]), True, green)
		ballrect = ball.get_rect()
		ballrect = ballrect.move(weathercenters[daycounter])	
		screen.blit(ball, ballrect)
		weathercounter +=1
		text = my_font1.render(str(weather[weathercounter]), True, green)
		textRect = text.get_rect(center=tempcenters[daycounter])  
		screen.blit(text, textRect)
		weathercounter +=1
		daycounter +=1
		
	
	pygame.display.flip()
	time.sleep(5)
		
def Todo_menu():
	global todo_more
	textinput = pygame_textinput.TextInput()
	clock = pygame.time.Clock()
	todoflag = True
	while todoflag:
		screen.fill((225, 225, 225))

		events = pygame.event.get()
		for event in events:
			if event.type == pygame.QUIT:
				exit()
				

		# Feed it with events every frame
		if textinput.update(events):
			todo_more = textinput.get_text()
			print(todo_more)
			todoflag = False
		# Blit its surface onto the screen
		screen.blit(textinput.get_surface(), (10, 10))

		pygame.display.update()
		clock.tick(30)
	
	
	
# refresh the stock info
refresh = True

while flag:  
	screen.fill(BLACK)
	screen.blit(background_surface, (0, 0))
	
	ui_manager.draw_ui(screen)
	#======================================================
	#
	#======================================================
	# display date and time
	
	
	date = display_date()
	# display a moon if its late in the night 
	if int(str(date[3][:2])) >= 21:
		ball1 = pygame.image.load("moon.png")
		ballrect1 = ball1.get_rect()
		ballrect1 = ballrect1.move(inits1)
		screen.blit(ball1, ballrect1)
	text = my_font1.render(str(date[3]), True, RED)
	textRect = text.get_rect(center=(250,30))  
	screen.blit(text, textRect)
	text = my_font1.render(str(date[0]), True, green)
	textRect = text.get_rect(center=(190,30))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[1]), True, WHITE)
	textRect = text.get_rect(center=(200,50))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[2]), True, WHITE)
	textRect = text.get_rect(center=(225,50))  
	screen.blit(text, textRect)
	text = my_font.render(str(date[4]), True, WHITE)
	textRect = text.get_rect(center=(260,50))  
	screen.blit(text, textRect)
	


	#======================================================
	# display functions   
	for my_text, text_pos in my_buttons.items():    
		text_surface = my_font.render(my_text, True, WHITE)    
		rect = text_surface.get_rect(center=text_pos)
		screen.blit(text_surface, rect) 

	#=======================================================
	#display weather
	#update frequency: every 10 minutes
	if counter == 0 or counter % 6000 == 0:
		weather = current_weather()
	if 'Rain' in weather[0] : 
		ball = pygame.image.load("Rain.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
		
	elif 'Sunny' in weather[0] or 'lear' in weather[0]:
		ball = pygame.image.load("sun.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'Wind' in weather[0]:
		ball = pygame.image.load("Wind.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'now' in weather[0]:
		ball = pygame.image.load("Snow.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'louds' in weather[0]:
		ball = pygame.image.load("Cloud.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	elif 'unde' in weather[0]:
		ball = pygame.image.load("Thunder.png")
		ballrect = ball.get_rect()
		ballrect = ballrect.move(inits)
	else:
		text = my_font1.render(str(weather[0]), True, green)
		textRect = text.get_rect(center=weather_position[0])  
		screen.blit(text, textRect)
	screen.blit(ball, ballrect)
	
	for i in range(1,3):
		text = my_font.render(str(weather[i]), True, WHITE)
		textRect = text.get_rect(center=weather_position[i])  
		screen.blit(text, textRect)
	#==================================================
	#display room temperature
	#update frequency: every 10 minutes	
	if counter == 0 or counter % 6000 == 0:
		temp_data = room_temp()
	temp = 'room temp: '+ str(temp_data) + '°C'
	text = my_font.render(temp, True, WHITE)
	textRect = text.get_rect(center=(60,60))  
	screen.blit(text, textRect)
	#=======================================================
	#print To-do list 

	htm_text_block_2 = UITextBox(todo_text+todo_more,
							 pygame.Rect((150, 70), (140, 130)),
							 manager=ui_manager,
							 object_id="#text_box_2")
	#=======================================================
	# print your portofolio
	#update every 10 minutes
	if counter == 0 or counter % 6000 == 0 or refresh:
		portofolio = 0
		if stocks:
			for items in stocks:
				price = stock_price(items)
				portofolio += float(price) * stocks[items]
		refresh = False
	text = my_font1.render('Portofolio: ', True, green)
	textRect = text.get_rect(center=(60,130))  
	screen.blit(text, textRect)
	text = my_font.render(str(portofolio)[:9], True, WHITE)
	textRect = text.get_rect(center=(60,160))  
	screen.blit(text, textRect)
	#======================================================
	# Detect touch on screen to determine next move
	for event in pygame.event.get():                
		if(event.type is MOUSEBUTTONUP):
			# Erase the Work space            
			pos = pygame.mouse.get_pos() 
			x,y = pos
			# go to the next level if pressed button.
			if y > 200:                
				if x < 160:    
					print ('quit button pressed')
					flag = False
					break
				else:
					function_menu()
			elif y < 40 and x < 110 :
				weather_menu()
			elif 70 < y < 150 and  100 < x < 250:
				Todo_menu()
			elif 120< y < 190 and 0 <x <130:
				stock_menu()
				refresh = True
	if(not time.time() - start < 300):
		flag = False
	pygame.display.flip() 
	#update counter every 0.1 second. Refresh counter every hour.
	time.sleep(0.1)
	counter += 1
	if counter > 36000:
		counter = 0